# -*- coding: utf-8 -*-

import re

from eva.interpreter import Environment
from eva.utils import storage
from eva.lexer import Lexer
from eva.parser import Parser
from eva.transform import Transformer

r_variable_name = re.compile(r'[a-zA-Z][a-zA-Z0-9_]*')


def fn_print(*args):
    print(*args)


global_env = Environment({
    'null': None,
    'true': True,
    'false': False,

    'VERSION': '0.1',

    '+': lambda x, y: x + y,
    '-': lambda x, y=None: y is None and -y or x - y,
    '*': lambda x, y: x * y,
    '/': lambda x, y: x / y,

    '>': lambda x, y: x > y,
    '>=': lambda x, y: x >= y,
    '<': lambda x, y: x < y,
    '<=': lambda x, y: x <= y,
    '=': lambda x, y: x == y,

    'print': fn_print,
})


class Eva:
    def __init__(self, _global=None):
        self._global = _global or global_env
        self._transformer = Transformer()

    def eval_code(self, code, env=None):
        lex = Lexer(code)
        parser = Parser(lex)
        exp = parser.parse()
        return self.eval(exp, env=env)

    def eval(self, exp, env=None):
        env = env or self._global

        if self._is_number(exp):
            return exp
        if self._is_string(exp):
            return exp[1:-1]

        if exp[0] == 'begin':
            block_env = Environment({}, env)
            return self._eval_block(exp, block_env)

        if exp[0] == 'var':
            _, name, value = exp
            return env.define(name, self.eval(value, env))
        if exp[0] == 'set':
            _, name, value = exp
            return env.assign(name, self.eval(value, env))

        if self._is_variable_name(exp):
            return env.lookup(exp)

        if exp[0] == 'if':
            _tag, condition, consequent, alternate = exp
            if self.eval(condition, env):
                return self.eval(consequent, env)
            else:
                return self.eval(alternate, env)

        if exp[0] == 'while':
            _tag, condition, body = exp
            result = None
            while self.eval(condition, env):
                result = self.eval(body, env)
            return result

        # Function declaration: (def square (x) (* x x))
        if exp[0] == 'def':
            _tag, name, params, body = exp
            fn = storage(name=name, params=params, body=body, env=env)
            return env.define(name, fn)

        if exp[0] == 'switch':
            if_exp = self._transformer.transform_switch_to_if(exp)
            return self.eval(if_exp, env)

        if exp[0] == 'for':
            while_exp = self._transformer.transform_for_to_while(exp)
            return self.eval(while_exp, env)

        if exp[0] == '++':
            set_exp = self._transformer.transform_inc_to_set(exp)
            return self.eval(set_exp, env)
        if exp[0] == '--':
            set_exp = self._transformer.transform_dec_to_set(exp)
            return self.eval(set_exp, env)
        if exp[0] == '+=':
            set_exp = self._transformer.transform_inc_val_to_set(exp)
            return self.eval(set_exp, env)
        if exp[0] == '-=':
            set_exp = self._transformer.transform_dec_val_to_set(exp)
            return self.eval(set_exp, env)

        # Lambda function: (lambda (x) (* x x))
        if exp[0] == 'lambda':
            _tag, params, body = exp
            return storage(params=params, body=body, env=env)

        if isinstance(exp, list):
            if isinstance(exp[0], list):
                fn = self.eval(exp[0], env)
            else:
                fn = env.lookup(exp[0])
            args = [self.eval(arg, env) for arg in exp[1:]]

            # 1. Native function:
            if not isinstance(fn, storage):
                return fn(*args)

            # 2. User-defined function:
            activation_record = {}
            for index, param in enumerate(fn.params):
                activation_record[param] = args[index]
            activation_env = Environment(activation_record, fn.env)
            return self._eval_body(fn.body, activation_env)

        raise NotImplementedError('NotImplemented: {}'.format(exp))

    def _is_number(self, exp):
        return isinstance(exp, int)

    def _is_string(self, exp):
        return isinstance(exp, str) and exp[0] == '"' and exp[-1] == '"'

    def _is_variable_name(self, exp):
        return isinstance(exp, str) and r_variable_name.match(exp)

    def _eval_block(self, block, env):
        result = None
        _, expressions = block[0], block[1:]
        for exp in expressions:
            result = self.eval(exp, env)
        return result

    def _eval_body(self, body, env):
        if body[0] == 'begin':
            return self._eval_block(body, env)
        return self.eval(body, env)
